
/*
 * Copyright (C) 2008-2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id: SidekarXMLView.java 246 2009-12-17 17:29:06Z archie.cobbs $
 */

package org.dellroad.sidekar.spring;

import java.io.OutputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dellroad.sidekar.Database;
import org.dellroad.sidekar.Item;
import org.dellroad.sidekar.schema.model.SchemaModel;
import org.dellroad.sidekar.util.SimpleIterator;
import org.dellroad.sidekar.xml.XMLExporter;
import org.springframework.web.servlet.view.AbstractView;

/**
 * Spring web view that marshals an object into XML.
 *
 * <p>
 * Supported object types are {@link Database}, {@link Item} (with a specific field optionally specified),
 * {@link Iterator Iterator<Item>}, and {@link SchemaModel},
 */
public class SidekarXMLView extends AbstractView {

    /**
     * The default content type: <code>{@value}</code>.
     */
    public static final String CONTENT_TYPE = "text/xml; charset=UTF-8";

    /**
     * The name of the model attribute holding the object to be rendered into XML.
     *
     * <p>
     * Object may be an {@link Database}, an {@link Item}, a {@link SchemaModel}, or an {@link Iterator Iterator<Item>}.
     */
    public static final String MARSHAL_OBJECT = "marshalObject";

    /**
     * The name of the model attribute holding the item field to render in the case of an {@link Item} marshal object;
     * if not present, all fields are rendered.
     */
    public static final String FIELD_NAME = "fieldName";

    /**
     * The name of the model attribute holding the maximum number of {@link Item}s to pull from an {@link Iterator}
     * marshal object. If present, the value of this attribute must be an {@link Integer}.
     */
    public static final String MAX_RESULTS = "maxResults";

    /**
     * Constructor.
     */
    public SidekarXMLView() {
        setContentType(CONTENT_TYPE);
    }

    /**
     * Render as XML.
     */
    @Override
    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Object obj = model.get(MARSHAL_OBJECT);
        if (obj == null)
            throw new NullPointerException("null or missing \"" + MARSHAL_OBJECT + "\" attribute in model");
        response.setContentType(getContentType());
        OutputStream out = response.getOutputStream();
        if (obj instanceof Database)
            XMLExporter.exportDatabase(out, (Database)obj, false);
        else if (obj instanceof SchemaModel)
            XMLExporter.exportSchema(out, (SchemaModel)obj);
        else if (obj instanceof Item) {
            String fieldName = (String)model.get(FIELD_NAME);
            XMLExporter.exportItem(out, (Item<?>)obj, fieldName != null ? Collections.singleton(fieldName) : null);
        } else if (obj instanceof Iterator) {
            int maxResults;
            try {
                maxResults = ((Integer)model.get(MAX_RESULTS)).intValue();
            } catch (Exception e) {
                maxResults = -1;
            }
            XMLExporter.exportItems(out, new LimitIterator((Iterator<?>)obj, maxResults));
        } else
            throw new IllegalArgumentException("invalid \"" + MARSHAL_OBJECT + "\" of type " + obj.getClass().getName());
        out.flush();
    }

    // Iterator that returns only so many elements
    static class LimitIterator extends SimpleIterator<Item<?>> {

        private final Iterator<?> i;
        private final int limit;

        private int count;

        LimitIterator(Iterator<?> i, int limit) {
            this.i = i;
            this.limit = limit;
        }

        public boolean hasNext() {
            return (this.limit < 0 || this.count < this.limit) && this.i.hasNext();
        }

        @Override
        protected Item<?> getNext() {
            Item<?> item = (Item<?>)this.i.next();
            this.count++;
            return item;
        }
    }
}

